
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 *
 * @author djk1076
 */
public class Individual implements Comparable<Individual> {

    private static int index = 0;
    private static final int DECAY_A_RATE_INDEX = index++;
    private static final int DECAY_B_RATE_INDEX = index++;
    private static final int ANT_A_COST_INDEX = index++;
    private static final int ANT_B_COST_INDEX = index++;
    private static final int ANT_A_CHILD_PROBABILITY_INDEX = index++;
    private static final int ANT_B_CHILD_PROBABILITY_INDEX = index++;
    private static final int NUM_ANT_A_INDEX = index++;
    private static final int NUM_ANT_B_INDEX = index++;
    private static final int LEFT_INDEX = index++;
    private static final int CENTER_HORIZONTAL_INDEX = index++;
    private static final int RIGHT_INDEX = index++;
    private static final int UP_INDEX = index++;
    private static final int CENTER_VERTICAL_INDEX = index++;
    private static final int DOWN_INDEX = index++;
    private static final int size = index++;
    private double vals[];
    private Double fitness = null;
    private int id;
    private static int nextID = 0;
    private int age = 0;
    private static final int[] INTS = {ANT_A_COST_INDEX, ANT_B_COST_INDEX, NUM_ANT_A_INDEX, NUM_ANT_B_INDEX};
    private double HORIZONTAL_TOTAL;//left+center+right
    private double VERTICAL_TOTAL;//up+center+down
    private double[] horizontal_move = new double[3];
    private double[] vertical_move = new double[3];
    private static double[] basic = {.005, .005, 5, 5, .005, .005, 5, 5, 1, 1, 1, 1, 1, 1};

    public static void main(String args[]) {



        List<Individual> population = new ArrayList();
        double[] start = basic;
        population.add(new Individual(start));
        for (int generation = 0; generation < 20; generation++) {
            int popSize = population.size();
            for (int i = 0; i < popSize; i++) {
                population.add(population.get(i).mutate());
            }

            population.add(population.get(0).crossover(population.get(1)));

            for (Individual i : population) {
                if (i.age > 3) {
                    i.fitness = 0.0;
                } else {
                    i.age++;
                }
            }
            Collections.sort(population);

            List<Individual> newPop = new ArrayList();
            newPop.addAll(population.subList(0, Math.min(5, population.size())));
            population = newPop;

            System.out.println("\n");
            System.out.println(generation + ":");
            for (Individual i : population) {
                System.out.println(i);
            }
        }
    }

    public Individual(String s) {
        this.id = nextID++;
        s = s.replace("[", "");
        s = s.replace("]", "");
        if (s.contains("=")) {//if already evaluated
            fitness = Double.parseDouble(s.substring(s.indexOf("=") + 1));
            if (fitness == 0) {
                fitness = null;
            }
            s = s.substring(0, s.indexOf("="));
            String[] receivedStrings = s.split(",");

            vals = new double[size];

            for (int i = 0; i < vals.length; i++) {
                vals[i] = Double.parseDouble(receivedStrings[i]);
            }
            adjustMove();
            HORIZONTAL_TOTAL = vals[LEFT_INDEX] + vals[CENTER_HORIZONTAL_INDEX] + vals[RIGHT_INDEX];

            VERTICAL_TOTAL = vals[UP_INDEX] + vals[CENTER_VERTICAL_INDEX] + vals[DOWN_INDEX];
            adjustInts();
        } else {//if needs to be evaluated

            String[] receivedStrings = s.split(",");
            vals = new double[receivedStrings.length];

            for (int i = 0; i < vals.length; i++) {
                vals[i] = Double.parseDouble(receivedStrings[i]);
            }
            adjustInts();
            adjustMove();
            new World(this).start();
//            new AntGUI(false, this);
        }
    }

    public Individual() {
        this.id = nextID++;
        vals = new double[size];
        for (int i = 0; i < size; i++) {
            vals[i] = Math.random() * 2 * basic[i];
        }
        adjustMove();
        adjustInts();
        fitness = -Double.MAX_VALUE;

        // new AntGUI(false, this);
    }

    public Individual(double[] vals) {
        this.id = nextID++;
        this.vals = new double[size];
        for (int i = 0; i < size; i++) {
            this.vals[i] = vals[i];
        }
        adjustMove();

        adjustInts();
        new World(this).start();
//        new AntGUI(false, this);
    }

    public int getID() {
        return id;
    }

    private void adjustInts() {
        if (Math.random() < .5) {
            for (int i : INTS) {
                vals[i] = Math.ceil(vals[i]);
            }
        } else {
            for (int i : INTS) {
                vals[i] = Math.floor(vals[i]);
                if (vals[i] <= 0) {
                    vals[i] = 1;
                }
            }
        }
    }

    private void adjustMove() {
        HORIZONTAL_TOTAL = vals[LEFT_INDEX] + vals[CENTER_HORIZONTAL_INDEX] + vals[RIGHT_INDEX];
        vals[LEFT_INDEX] /= HORIZONTAL_TOTAL;
        vals[CENTER_HORIZONTAL_INDEX] /= HORIZONTAL_TOTAL;
        vals[RIGHT_INDEX] /= HORIZONTAL_TOTAL;
        horizontal_move[0] = vals[LEFT_INDEX];
        horizontal_move[1] = vals[CENTER_HORIZONTAL_INDEX];
        horizontal_move[2] = vals[RIGHT_INDEX];

        VERTICAL_TOTAL = vals[UP_INDEX] + vals[CENTER_VERTICAL_INDEX] + vals[DOWN_INDEX];
        vals[UP_INDEX] /= VERTICAL_TOTAL;
        vals[CENTER_VERTICAL_INDEX] /= VERTICAL_TOTAL;
        vals[DOWN_INDEX] /= VERTICAL_TOTAL;
        vertical_move[0] = vals[UP_INDEX];
        vertical_move[1] = vals[CENTER_VERTICAL_INDEX];
        vertical_move[2] = vals[DOWN_INDEX];


    }

    public double[] getHorizontal() {
        return horizontal_move;
    }

    public double[] getVertical() {
        return vertical_move;
    }

    public int compareTo(Individual arg0) {
        return arg0.getFitness().compareTo(getFitness());
    }

    public Comparable getFitness() {
        while (fitness == null) {
            try {
                //   System.out.println("sleeping");
                Thread.yield();
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
        }
        return fitness;
    }

    public String toString() {
        return id + ":" + Arrays.toString(vals) + ":" + fitness;
    }

    public Individual crossover(Individual other) {
        double[] newVal = new double[size];
        int cross = (int) (Math.random() * size);
        for (int i = 0; i < cross; i++) {
            newVal[i] = vals[i];
        }
        for (int i = cross; i < size; i++) {
            newVal[i] = other.vals[i];
        }

        return new Individual(newVal);

    }

    public double[] crossoverGenes(double[] other) {
        double[] newVal = new double[size];
        int cross = (int) (Math.random() * size);
        for (int i = 0; i < cross; i++) {
            newVal[i] = vals[i];
        }
        for (int i = cross; i < size; i++) {
            newVal[i] = other[i];
        }
        return newVal;
    }

    public Individual mutate() {


        double[] newVal = new double[size];
        for (int i = 0; i < size; i++) {
            newVal[i] = vals[i] * ((Math.random() + .5));
            if (newVal[i] <= 0) {
                newVal[i] = 1;
            }
        }

        return new Individual(newVal);
    }

    public double[] mutateGenes() {

        double[] newVal = new double[size];
        for (int i = 0; i < size; i++) {

            newVal[i] = vals[i] * ((Math.random() + .5));
            if (newVal[i] <= 0) {
                newVal[i] = 1;
            }
        }
        return newVal;
    }

    public double getDecay_A_Rate() {
        return vals[DECAY_A_RATE_INDEX];
    }

    public double getDecay_B_Rate() {
        return vals[DECAY_B_RATE_INDEX];
    }

    public int getNum_A_Ants() {
        if (vals[NUM_ANT_A_INDEX] < 1) {
            vals[NUM_ANT_A_INDEX] = 1;
        }


        return (int) vals[NUM_ANT_A_INDEX];
    }

    public int getNum_B_Ants() {
        if (vals[NUM_ANT_B_INDEX] < 1) {
            vals[NUM_ANT_B_INDEX] = 1;
        }


        return (int) vals[NUM_ANT_B_INDEX];
    }

    public int getAnt_A_Cost() {
        return (int) (vals[ANT_A_COST_INDEX]);
    }

    public int getAnt_B_Cost() {
        return (int) (vals[ANT_B_COST_INDEX]);
    }

    public double getProbabilityOfAnt_A_Child() {
        return vals[ANT_A_CHILD_PROBABILITY_INDEX];
    }

    public double getProbabilityOfAnt_B_Child() {
        return vals[ANT_B_CHILD_PROBABILITY_INDEX];
    }

    void reportFitness(int gathered, int total) {
        fitness = gathered * 1.0 / total;
    }

    double[] getValues() {
        return vals;
    }
}
